import os
import sys
import subprocess
from pathlib import Path
from dotenv import load_dotenv
import logging
import traceback
import json
from datetime import datetime
from typing import List, Dict, Optional, Union
from dataclasses import dataclass
from concurrent.futures import ThreadPoolExecutor, as_completed

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('gitee_commit.log'),
        logging.StreamHandler(sys.stdout)
    ]
)

@dataclass
class GitConfig:
    """Git配置信息"""
    user: str
    token: str
    org: str
    project_prefix: str
    branch: str
    base_path: str
    logs_path: str
    force_push: bool = False
    debug_mode: bool = False
    max_worker: int = 1
    commit_only_spec_files: bool = False # 新增配置项


@dataclass
class RepositoryInfo:
    """仓库信息"""
    directory: str
    url: str
    branch: str
    error: Optional[str] = None

class GitOperationError(Exception):
    """Git操作异常"""
    pass

class ConfigError(Exception):
    """配置错误"""
    pass

def load_config() -> GitConfig:
    """加载配置信息"""
    try:
        load_dotenv()

        config = GitConfig(
            user=os.getenv('gitee_user'),
            token=os.getenv('gitee_token'),
            org=os.getenv('gitee_org'),
            project_prefix=os.getenv('gitee_project_prefix'),
            branch=os.getenv('GITEE_BRANCH', 'master'),
            base_path=os.getenv('PUSH_BASE_PATH'),
            force_push=os.getenv('IS_FORCE_PUSH', 'FALSE').upper() == 'TRUE',
            logs_path=os.getenv('LOGS_PATH',"logs"),
            debug_mode=os.getenv('debug_mode', 'FALSE').upper() == 'TRUE',
            max_worker=int(os.getenv('max_worker', 1)), # 确保是整数
            commit_only_spec_files=os.getenv('COMMIT_ONLY_SPEC_FILES', 'FALSE').upper() == 'TRUE' # 加载新配置
        )

        # 验证配置
        missing_fields = []
        for field, value in config.__dict__.items():
            # 调整检查逻辑以适应布尔值和整数
            if value is None or (isinstance(value, str) and not value):
                 # 排除可以为 False 或 0 的字段
                if field not in ['force_push', 'debug_mode', 'commit_only_spec_files']:
                     missing_fields.append(field)
            elif isinstance(value, int) and value == 0 and field == 'max_worker': # max_worker 不能为 0
                 missing_fields.append(field)


        if missing_fields:
            raise ConfigError(f"缺少或无效的配置项: {', '.join(missing_fields)}")

        return config
    except Exception as e:
        logging.error(f"加载配置失败: {str(e)}")
        raise

def save_submission_record(repo_info: RepositoryInfo,config: GitConfig,is_success: bool = True) -> None:
    """保存提交记录"""
    logs_path = config.logs_path
    try:
        record_file = 'successful_submissions.json' if is_success else 'failed_submissions.json'
        record_file = os.path.join(logs_path,record_file)
        # 读取现有记录
        if os.path.exists(record_file):
            with open(record_file, 'r', encoding='utf-8') as f:
                records = json.load(f)
        else:
            records = []
            
        # 构建记录
        record = {
            'timestamp': datetime.now().isoformat(),
            'directory': repo_info.directory,
            'repository_url': repo_info.url,
            'branch': repo_info.branch
        }
        
        if not is_success and repo_info.error:
            record['error_message'] = repo_info.error
            
        records.append(record)
        
        # 保存记录
        with open(record_file, 'w', encoding='utf-8') as f:
            json.dump(records, f, indent=2, ensure_ascii=False)
            
        log_level = logging.INFO if is_success else logging.ERROR
        logging.log(log_level, f"记录已保存到 {record_file}")
    except Exception as e:
        logging.error(f"保存记录失败: {str(e)}")
        logging.debug(traceback.format_exc())

def get_first_level_dirs(base_path: str) -> List[str]:
    """获取指定目录下的一级文件夹"""
    try:
        if not os.path.exists(base_path):
            raise FileNotFoundError(f"目录不存在: {base_path}")
            
        if not os.path.isdir(base_path):
            raise NotADirectoryError(f"路径不是目录: {base_path}")
            
        dirs = [d for d in os.listdir(base_path) 
                if os.path.isdir(os.path.join(base_path, d))]
                
        if not dirs:
            logging.warning(f"目录 {base_path} 下没有找到子目录")
            
        return dirs
    except Exception as e:
        logging.error(f"获取目录列表失败: {str(e)}")
        raise

def run_git_command(cmd: List[str], capture_output: bool = True) -> Optional[str]:
    """执行Git命令"""
    try:
        result = subprocess.run(cmd, 
                              check=True, 
                              capture_output=capture_output, 
                              text=True)
        return result.stdout.strip() if capture_output else None
    except subprocess.CalledProcessError as e:
        error_msg = e.stderr.strip() if e.stderr else str(e)
        raise GitOperationError(f"Git命令执行失败: {error_msg}")

def initialize_gitee_repo(repo_path: str, add_only_spec_files: bool = False) -> bool:
    """初始化Gitee仓库，并根据参数添加文件"""
    try:
        if not os.path.exists(repo_path):
            raise FileNotFoundError(f"仓库路径不存在: {repo_path}")

        os.chdir(repo_path)
        logging.info(f"切换到目录: {repo_path}")

        if os.path.exists('.git'):
            run_git_command(['rm', '-rf', '.git'])
            logging.warning(f"目录 {repo_path} 已经是git仓库,删除.git目录，重新初始化")

        run_git_command(['git', 'init'])
        logging.info(f"Git仓库初始化成功: {repo_path}")

        commit_message = "Initial commit"
        if add_only_spec_files:
            # 只添加 .spec 文件
            try:
                spec_files = list(Path('.').glob('*.spec'))
                if not spec_files:
                    logging.warning("没有找到 .spec 文件，暂存区将为空")
                    # 即使没有 spec 文件也继续，以便创建空仓库或处理其他逻辑
                else:
                    run_git_command(['git', 'add'] + [str(f) for f in spec_files])
                    logging.info(f"已添加 {len(spec_files)} 个 .spec 文件到暂存区")
                    commit_message = "Initial commit with .spec files"

            except Exception as e:
                 logging.error(f"添加 .spec 文件时出错: {str(e)}")
                 raise # 重新抛出异常，以便上层捕获
        else:
            # 添加所有文件
            run_git_command(['git', 'add', '.'])
            logging.info("所有文件已添加到暂存区")
            commit_message = "Initial commit (all files)"


        # 检查是否有文件被暂存
        if not run_git_command(['git', 'status', '--porcelain']):
            logging.warning("没有文件需要提交 (暂存区为空)")
            # 即使没有文件提交，也可能需要关联远程仓库并推送空分支或检查分支
            # 因此这里返回 True，让后续流程继续
            return True # 返回 True 表示初始化过程（即使没有文件）是成功的

        run_git_command(['git', 'commit', '-m', commit_message])
        logging.info(f"初始提交完成: {commit_message}")

        return True
    except Exception as e:
        logging.error(f"初始化仓库失败: {str(e)}")
        raise

def connect_remote_repo(repo_url: str, username: str, token: str) -> bool:
    """关联远程仓库"""
    try:
        result = run_git_command(['git', 'remote', '-v'])
        
        if 'origin' in result:
            logging.warning("远程仓库已存在，将更新URL")
            run_git_command(['git', 'remote', 'set-url', 'origin', 
                           repo_url.replace('https://', f'https://{username}:{token}@')])
        else:
            auth_repo_url = repo_url.replace('https://', f'https://{username}:{token}@')
            run_git_command(['git', 'remote', 'add', 'origin', auth_repo_url])
            
        logging.info(f"远程仓库 {repo_url} 关联成功")
        return True
    except Exception as e:
        logging.error(f"关联远程仓库失败: {str(e)}")
        raise

def check_and_create_branch(branch: str) -> bool:
    """检查并创建远程分支"""
    try:
        result = run_git_command(['git', 'ls-remote', '--heads', 'origin'])
        
        if f"refs/heads/{branch}" not in result:
            logging.info(f"远程分支 {branch} 不存在，正在创建...")
            run_git_command(['git', 'checkout', '-b', branch])
            run_git_command(['git', 'push', '-u', 'origin', branch])
            logging.info(f"远程分支 {branch} 创建成功")
        else:
            logging.info(f"远程分支 {branch} 已存在")
            run_git_command(['git', 'checkout', branch])
            
        return True
    except Exception as e:
        logging.error(f"检查/创建分支失败: {str(e)}")
        raise

def handle_conflicts(repo_path: str, branch: str) -> None:
    """处理冲突并记录到文件"""
    try:
        # 获取冲突文件列表
        conflict_files = run_git_command(['git', 'diff', '--name-only', '--diff-filter=U'])
        if not conflict_files:
            return
            
        # 创建冲突记录文件
        conflict_file = os.path.join(repo_path, 'conflicts.json')
        conflicts = []
        
        for file in conflict_files.split('\n'):
            # 获取冲突内容
            conflict_content = run_git_command(['git', 'diff', file])
            
            conflicts.append({
                'file': file,
                'content': conflict_content,
                'timestamp': datetime.now().isoformat()
            })
            
        # 保存冲突信息
        with open(conflict_file, 'w', encoding='utf-8') as f:
            json.dump(conflicts, f, indent=2, ensure_ascii=False)
            
        logging.error(f"发现冲突，已记录到 {conflict_file}")
    except Exception as e:
        logging.error(f"处理冲突时出错: {str(e)}")
        raise

def push_to_remote(branch: str, force: bool = False) -> bool:
    """推送代码到远程仓库"""
    try:
        # 检查远程分支是否存在
        result = run_git_command(['git', 'ls-remote', '--heads', 'origin'])
        run_git_command(['git', 'checkout', '-b', branch])
        if f"refs/heads/{branch}" in result:
            if force:
                # 强制推送
                run_git_command(['git', 'push', '-f', 'origin', branch])
                logging.warning(f"已强制推送到 {branch} 分支")
            else:
                try:
                    # 先拉取更新
                    run_git_command(['git', 'pull', 'origin', branch])
                    logging.info(f"已从 {branch} 分支拉取更新")
                    
                    # 检查是否有冲突
                    status = run_git_command(['git', 'status', '--porcelain'])
                    if 'UU' in status:  # 存在冲突
                        handle_conflicts(os.getcwd(), branch)
                        return False
                        
                    # 推送更新
                    run_git_command(['git', 'push', 'origin', branch])
                    logging.info(f"已推送到 {branch} 分支")
                except GitOperationError as e:
                    if 'conflict' in str(e).lower():
                        handle_conflicts(os.getcwd(), branch)
                        return False
                    raise
        else:
            # 创建并推送新分支
            run_git_command(['git', 'checkout', '-b', branch])
            run_git_command(['git', 'push', '-u', 'origin', branch])
            logging.info(f"已创建并推送到新分支 {branch}")
            
        return True
    except Exception as e:
        logging.error(f"推送代码失败: {str(e)}")
        raise

def process_directory(config: GitConfig, dir_name: str, add_only_spec_files: bool) -> RepositoryInfo:
    """处理单个目录"""
    repo_info = RepositoryInfo(
        directory=dir_name,
        url=f"{config.project_prefix}/{config.org}/{dir_name}.git", # 修正 URL 格式
        branch=config.branch
    )

    original_cwd = os.getcwd() # 保存原始工作目录
    try:
        logging.info(f"\n开始处理目录: {dir_name}")

        repo_path = os.path.join(config.base_path, dir_name)

        # 将 add_only_spec_files 参数传递给初始化函数
        if not initialize_gitee_repo(repo_path, add_only_spec_files=add_only_spec_files):
            # 如果 initialize_gitee_repo 返回 False（例如没有 spec 文件且需要停止），可以在这里处理
            # raise Exception("初始化仓库失败或未找到指定文件")
            # 根据 initialize_gitee_repo 的实际返回值逻辑调整
             pass # 当前实现下，即使没有文件也会返回 True

        if not connect_remote_repo(repo_info.url, config.user, config.token):
            raise Exception("关联远程仓库失败")

        # push_to_remote 现在可能需要处理空提交的情况，但 git push 通常能处理
        if not push_to_remote(config.branch, config.force_push):
             # push_to_remote 返回 False 通常意味着有冲突
            raise Exception("推送代码失败或检测到冲突")

        save_submission_record(repo_info,config, is_success=True)
        logging.info(f"目录处理完成: {dir_name}")

        return repo_info
    except Exception as e:
        error_message = str(e)
        logging.error(f"处理目录 {dir_name} 失败: {error_message}")
        logging.debug(traceback.format_exc())

        repo_info.error = error_message
        save_submission_record(repo_info, config, is_success=False,)

        return repo_info
    finally:
        os.chdir(original_cwd) # 确保切回原始目录
        logging.debug(f"已切回目录: {original_cwd}")


def process_directories(config: GitConfig, add_only_spec_files: bool) -> None:
    """处理所有目录"""
    try:
        dirs = get_first_level_dirs(config.base_path)
        if not dirs:
            logging.warning("未找到任何子目录")
            return

        logging.info(f"找到 {len(dirs)} 个目录需要处理")
        if add_only_spec_files:
            logging.info("当前模式: 只提交 .spec 文件")
        else:
            logging.info("当前模式: 提交所有文件")


        # 调试模式下，只处理前几个目录
        if config.debug_mode:
            limit = 2 # 例如只处理前2个
            dirs = dirs[:limit]
            logging.info(f"调试模式下，只处理前 {limit} 个目录: {dirs}")

        # 使用线程池并行处理目录
        with ThreadPoolExecutor(max_workers=min(len(dirs), config.max_worker)) as executor:
            # 将 add_only_spec_files 参数传递给 submit
            futures = {
                executor.submit(process_directory, config, dir_name, add_only_spec_files): dir_name
                for dir_name in dirs
            }

            success_count = 0
            processed_count = 0
            total_dirs = len(futures) # 使用 futures 的长度作为总数

            for future in as_completed(futures):
                processed_count += 1
                dir_name = futures[future]
                try:
                    repo_info = future.result()
                    if not repo_info.error:
                        success_count += 1
                        logging.info(f"({processed_count}/{total_dirs}) 成功处理目录: {dir_name}")
                    else:
                        logging.error(f"({processed_count}/{total_dirs}) 处理目录失败: {dir_name} - {repo_info.error}")
                except Exception as e:
                    logging.error(f"({processed_count}/{total_dirs}) 处理目录 {dir_name} 时发生严重异常: {str(e)}")
                    logging.debug(traceback.format_exc()) # 记录详细堆栈

        logging.info(f"处理完成: 成功 {success_count}/{total_dirs} 个目录")
    except Exception as e:
        logging.error(f"处理目录时发生错误: {str(e)}")
        raise

def main():
    try:
        config = load_config()
        # 将配置值传递给 process_directories
        process_directories(config, config.commit_only_spec_files)
    except ConfigError as e:
        logging.error(f"配置错误: {str(e)}")
        sys.exit(2) # 使用不同的退出码表示配置错误
    except Exception as e:
        logging.error(f"程序执行失败: {str(e)}")
        logging.debug(traceback.format_exc())
        sys.exit(1)

if __name__ == "__main__":
    main()